#include "kstd.h"
#include "kfs.h"
#include "stdio.h"
#include "string.h"

static t_superblock* super = 0;
static t_file_descriptor fd_table[FD_MAX];

/* Return the checksum of any data
	data: pointer on the data you want the checksum of
	length: length of the data in bytes
*/
static t_uint32 adler32(void* data, t_uint32 length)
{
  char* d = data;
  t_uint32 i;
  t_uint32 A = 1;
  t_uint32 B = 0;

  for (i = 0; i < length; ++i, ++d)
    {
      A = (*d + A) % 65521;
      B = (A + B) % 65521;
    }

  B = B << 16;
  return (B | A);
}

static int check_data_block(t_data_block* block)
{
  t_uint32 in_mem_sum = (*block).checksum;
  (*block).checksum = 0;
  t_uint32 calc_sum = adler32(block, sizeof(t_data_block));
  (*block).checksum = in_mem_sum;

  if (in_mem_sum != calc_sum)
    {
      printf("\n\nError: File system data block corrupted.\n\n");
      printf("\tChecksum are different:\n");
      printf("\tMemory checksum: %x    |    Calculated Checksum: %x\n",
	     in_mem_sum, calc_sum);
      return 0;
    }

  return 1;
}

static int check_indirect(t_indirect_block* block)
{
  t_uint32 in_mem_sum = (*block).checksum;
  t_uint32 calc_sum = adler32(block, sizeof(t_indirect_block) -
			      sizeof (t_uint32));

  if (in_mem_sum != calc_sum)
    {
      printf("\n\nError: File system indirect block corrupted.\n\n");
      printf("\tChecksum are different:\n");
      printf("\tMemory checksum: %x    |    Calculated Checksum: %x\n",
	     in_mem_sum, calc_sum);
      return 0;
    }

  return 1;
}

void kfs_init(unsigned int super_addr)
{
  int i;
  super = (t_superblock*) super_addr;

  // Get the checksum of the superblock from memory
  t_uint32 in_mem_sum = (*super).checksum;
  // Compute a new checksum, ignoring the 'checksum' field of course.
  t_uint32 calc_sum = adler32(super, sizeof(t_superblock) - sizeof(t_uint32));

  // Crash if superblock is corrupted. Maybe replace it by an interrupt ?
  if (in_mem_sum != calc_sum)
    {
      printf("\n\nFatal error: File system superblock corrupted.\n\n");
      printf("\tChecksum are different:\n");
      printf("\tMemory checksum: %x    |    Calculated Checksum: %x\n",
	     in_mem_sum, calc_sum);
      while(1);
    }

  for (i = 0; i < FD_MAX; ++i)
    fd_table[i].id = -1;
}

int open(const char* pathname, int flags)
{
  int i, j;
  int count;
  char* pos = (char*)super;
  t_i_node* curr_node;
  t_data_block* data_block;

  for (i = 0; (i < FD_MAX) && (fd_table[i].id != -1); ++i);

  if (flags != O_RDONLY)
    {
      printf("\nError: Can't open file.\n");
      printf("\tO_RDONLY is the only supported flag..");
      return -1;
    }

  if (i >= FD_MAX)
    {
      printf("\nError: Can't open file.\n");
      printf("\tNo file descriptor available.\n");
      return -1;
    }

  count = (*super).i_nodes_count;
  pos += 4096 * (*super).first_i_node_block_index;

  for (j = 0; j < count; ++j)
    {
      curr_node = (t_i_node*)pos;

      if (strcmp((*curr_node).filename, pathname) == 0)
	{
	  t_uint32 in_mem_sum = (*curr_node).checksum;
	  t_uint32 calc_sum = adler32(curr_node, sizeof(t_i_node) -
				      sizeof(t_uint32));
	  if (in_mem_sum != calc_sum)
	    {
	      printf("\nError: Can't open file.\n\n");
	      printf("\tFile corrupted, checksum are different:\n");
	      printf("\tMemory checksum: %x    |    Calculated Checksum: %x\n",
		     in_mem_sum, calc_sum);
	     return -1;
	    }
	  fd_table[i].id = i;
	  fd_table[i].i_node = curr_node;
	  fd_table[i].curr_indirect = 0;
	  fd_table[i].curr_indirect_i = 0;
	  data_block = (t_data_block*)((char*)super +
				       (4096 *
					(*curr_node).direct_blocks_indexes[0]));
	  if (!check_data_block(data_block))
	    return -1;
	  fd_table[i].curr_data_block_i = 0;
	  fd_table[i].curr_data_block = data_block;
	  fd_table[i].cursor = (*data_block).data;
	  fd_table[i].pos = 0;
	  return i;
	}

      pos = (char*)super;
      pos += 4096 * (*curr_node).next_i_node_block_index;
    }

  printf("\nError: Can't open file.\n");
  printf("\tFile not found.\n");
  return -1;
}

static t_data_block* get_data_block(t_uint32 index)
{
  return (t_data_block*)((char*)super + (4096 * index));
}

static t_indirect_block* get_indirect_block(t_uint32 index)
{
  return (t_indirect_block*)((char*)super + (4096 * index));
}

static int next_block(int fd)
{
  t_i_node* i_node = fd_table[fd].i_node;
  fd_table[fd].curr_data_block_i++;

  if (fd_table[fd].curr_indirect == 0)
    {
      if (fd_table[fd].curr_data_block_i >=
	  ((*i_node).direct_blocks_count))
	{
	  // Goto first indirect
	  if ((*i_node).indirect_blocks_count == 0)
	    return 0;
	  fd_table[fd].curr_indirect_i = 0;
	  fd_table[fd].curr_indirect =
	    get_indirect_block(
			       (*i_node)
			       .indirect_blocks_indexes[fd_table[fd]
							.curr_indirect_i]);
	  fd_table[fd].curr_data_block_i = 0;
	}
      else
	{
	  // Goto next direct in i-node
	  fd_table[fd].curr_data_block =
	    get_data_block(
			   (*i_node)
			   .direct_blocks_indexes[fd_table[fd]
						  .curr_data_block_i]);
	}
    }

  if (fd_table[fd].curr_indirect != 0)
    {
      t_indirect_block* indirect = fd_table[fd].curr_indirect;

      if (fd_table[fd].curr_data_block_i >=
	  ((*indirect).blocks_count))
	{
	  // Goto next indirect
	  fd_table[fd].curr_indirect_i++;

	  if (fd_table[fd].curr_indirect_i >=
	      ((*i_node).indirect_blocks_count))
	    return 0;

	  fd_table[fd].curr_indirect =
	    get_indirect_block(
			       (*i_node)
			       .indirect_blocks_indexes[fd_table[fd]
							.curr_indirect_i]);
	  fd_table[fd].curr_data_block_i = 0;
	}

      if (fd_table[fd].curr_data_block_i <
	  ((*indirect).blocks_count))
	{
	  // Goto next direct in indirect
	  fd_table[fd].curr_data_block =
	    get_data_block(
			   (*indirect)
			   .data_blocks_indexes[fd_table[fd]
						.curr_data_block_i]);
	}

      if (!check_indirect(fd_table[fd].curr_indirect))
	return -1;
    }

  if (!check_data_block(fd_table[fd].curr_data_block))
    return -1;

  fd_table[fd].pos = 0;
  fd_table[fd].cursor = (*(fd_table[fd].curr_data_block)).data;

  return 1;
}

static int prev_block(int fd)
{
  t_i_node* i_node = fd_table[fd].i_node;

  if (fd_table[fd].curr_indirect != 0)
    {
      t_indirect_block* indirect = fd_table[fd].curr_indirect;

      if (fd_table[fd].curr_data_block_i == 0)
	{
	  // Goto previous indirect

	  if (fd_table[fd].curr_indirect_i > 0)
	    {
	      fd_table[fd].curr_indirect_i--;
	      fd_table[fd].curr_indirect =
		get_indirect_block(
				   (*i_node)
				   .indirect_blocks_indexes[fd_table[fd]
							    .curr_indirect_i]);
	      fd_table[fd].curr_data_block_i = (*indirect).blocks_count - 1;
	      if (!check_indirect(fd_table[fd].curr_indirect))
		return -1;
	    }
	  else
	    {
	      fd_table[fd].curr_data_block_i = (*i_node).direct_blocks_count - 1;
	      fd_table[fd].curr_indirect = 0;
	      fd_table[fd].curr_indirect_i = 0;
	    }
	}

      if (fd_table[fd].curr_indirect)
	{
	  // Goto previous direct in indirect
	  fd_table[fd].curr_data_block =
	    get_data_block(
			   (*indirect)
			   .data_blocks_indexes[fd_table[fd]
						.curr_data_block_i]);
	  if (!check_indirect(fd_table[fd].curr_indirect))
	    return -1;
	}
    }

  if (fd_table[fd].curr_indirect == 0)
    {
      if (fd_table[fd].curr_data_block_i > 0)
	fd_table[fd].curr_data_block_i--;

      // Goto previous direct in i-node
      fd_table[fd].curr_data_block =
	get_data_block(
		       (*i_node)
		       .direct_blocks_indexes[fd_table[fd]
					      .curr_data_block_i]);
    }


  if (!check_data_block(fd_table[fd].curr_data_block))
    return -1;

  fd_table[fd].pos = (*(fd_table[fd].curr_data_block)).block_usage - 1;
  fd_table[fd].cursor = &((*(fd_table[fd].curr_data_block))
			  .data[fd_table[fd].pos]);

  return 1;
}


ssize_t read(int fd, void* buf, size_t count)
{
  t_uint32 data_max = (*(fd_table[fd].curr_data_block)).block_usage;
  char* res = (char*)buf;
  ssize_t nb = 0;
  size_t i;
  int ret;

  for (i = 0; i < count; ++i, nb++, (fd_table[fd].pos)++)
    {
      if (fd_table[fd].pos >= data_max)
	{
	  ret = next_block(fd);
	  if (ret == -1)
	    return -1;

	  if (!ret)
	    return nb;

	  data_max = (*(fd_table[fd].curr_data_block)).block_usage;
	}

      res[i] = (*(fd_table[fd].curr_data_block)).data[fd_table[fd].pos];
    }

  return nb;
}

off_t seek(int fd, off_t offset, int whence)
{
  t_data_block* data_block;
  t_i_node* i_node = fd_table[fd].i_node;
  off_t res = 0;
  t_uint32 data_size;

  if (whence == SEEK_SET)
    {
      fd_table[fd].curr_indirect = 0;
      fd_table[fd].curr_indirect_i = 0;
      data_block = (t_data_block*)((char*)super +
				   (4096 *
				    (*i_node).direct_blocks_indexes[0]));
      if (!check_data_block(data_block))
	return -1;
      fd_table[fd].curr_data_block_i = 0;
      fd_table[fd].curr_data_block = data_block;
      fd_table[fd].cursor = (*data_block).data;
      fd_table[fd].pos = 0;
      res = 0;
    }
  else
    {
      printf("SEEK MOD not handled yet...\n");
      while(1);
    }

  while (offset > 0)
    {
      data_size = (*(fd_table[fd].curr_data_block)).block_usage;
      if (offset < data_size)
	{
	  fd_table[fd].pos = offset;
	  fd_table[fd].cursor = &((*(fd_table[fd].curr_data_block))
	    .data[fd_table[fd].pos]);
	  res += offset;
	  return res;
	}
      else
	{
	  if (next_block(fd) > 0)
	    {
	      offset -= data_size;
	      res += data_size;
	    }
	  else
	    {
	      fd_table[fd].pos = data_size - 1;
	      fd_table[fd].cursor = &((*(fd_table[fd].curr_data_block))
				      .data[fd_table[fd].pos]);
	      res += data_size - 1;
	      return res;
	    }
	}
    }

  return res;
}

int close(int fd)
{
  if ((fd < 0) || (fd > FD_MAX) || (fd_table[fd].id < 0))
    return -1;

  fd_table[fd].id = -1;
  return 0;
}
